home *** CD-ROM | disk | FTP | other *** search
/ Giga Games 1 / Giga Games.iso / net / go / comp / util.cc < prev    next >
Encoding:
C/C++ Source or Header  |  1991-06-05  |  25.8 KB  |  691 lines

  1. // copying
  2.     // Copyright (C) 1991 David Stoutamire (daves@alpha.ces.cwru.edu)
  3.     //
  4.     // This program is free software; you can redistribute it and/or modify
  5.     // it under the terms of the GNU General Public License as published by
  6.     // the Free Software Foundation, version 2.
  7.     // 
  8.     // This program is distributed in the hope that it will be useful,
  9.     // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10.     // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11.     // GNU General Public License for more details.
  12.     // 
  13.     // You should have received a copy of the GNU General Public License
  14.     // along with this program (file "COPYING"); if not, write to the Free
  15.     // Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  16.  
  17. #include "iku.h"
  18.  
  19. // class Pos
  20.     // class Pos simply indicates a position on the board.
  21.     // This may also be 'invalid'.
  22.     // constructors
  23.         Pos::Pos(String s) {
  24.             // construct from string in Korschelt notation...
  25.             char c=s[0];
  26.             int r=atoi((char*)String(s) + 1);
  27.         
  28.             if (isupper(c)) 
  29.                 c=tolower(c);
  30.             if (c<'a'||c=='i'||c>'t'||r<1||r>19) {   // invalid position
  31.                 row=(-1);
  32.                 col=(-1);
  33.                 }                
  34.             else {
  35.                 if (c>'i') c--;
  36.                 row=r-1;
  37.                 col=c-'a';
  38.                 }
  39.             }
  40.         Pos::Pos(int r, int c) {
  41.             // Note that although Korschelt notation is <col,row>,
  42.         // all references
  43.             // with explicit numbers are given <row,col>.
  44.             row=r;
  45.             col=c;
  46.             }
  47.         Pos::Pos() {
  48.             // Any row, col of (-1) signals a bad position
  49.             row=(-1);
  50.             col=(-1);
  51.             }
  52.     // members
  53.         bool Pos::valid() {
  54.             // is it a valid position?
  55.             return(row>=0&&row<=18&&col>=0&&col<=18);
  56.             }
  57.         bool Pos::operator== (Pos vs) {
  58.             return(row==vs.row&&col==vs.col);
  59.             }
  60.         bool Pos::operator!= (Pos vs) {
  61.             return(row!=vs.row||col!=vs.col);
  62.             }
  63.  
  64.         // up, down, left and right return corresponding positions.
  65.         // these MAY be invalid...
  66.         Pos Pos::up()      { return Pos(row+1,col); }
  67.         Pos Pos::down()    { return Pos(row-1,col); }
  68.         Pos Pos::left()    { return Pos(row,col-1); }
  69.         Pos Pos::right()   { return Pos(row,col+1); }
  70.     // friends
  71.         ostream& operator<< (ostream& s, Pos p) {
  72.             s << p.str();
  73.             return(s);
  74.             }
  75.         istream& operator>> (istream& s, Pos& p) {
  76.             // Positions may be >> with the caveat that after a bad read
  77.             // (an invalid position) future reads may all be bad. 
  78.             // Watch out for infinite reading-in loops!
  79.             char c;
  80.             int r;
  81.         
  82.             s >> c >> r;
  83.             p=Pos(String(c)+String(r));
  84.             return(s);
  85.             }
  86.         String Pos::str() {
  87.             // Moves are printed in Korschelt or as the string "BadPos".
  88.             if (!valid())
  89.                 return("BadPos");
  90.             else
  91.                 return String(chr('a'+col+((col>7)?1:0)))+String(dec(row+1));
  92.             }
  93. // class Move
  94.     // class Move is a move, like a play, resignation, pass, etc.
  95.     // May be 'invalid'.
  96.     // constructors
  97.         Move::Move() {
  98.             // constructor for invalid (default) move
  99.             kind=invalid;
  100.             where=Pos();
  101.             }
  102.         Move::Move(moveenum k,Pos at=Pos()) {
  103.             // basic constructor
  104.             kind=k;
  105.             where=at;
  106.             }
  107.         Move::Move(String s) {
  108.             // Attempts to translate s into a move.
  109.             // If something is wrong, returns
  110.             // an invalid move.  Check with valid().        
  111.             // Can parse COSMOS-style and Ishi Standard Format moves.
  112.             s=s.after(
  113.                 Regex("^[ \n\t]*[0-9]*[ \n\t]*[BWbw][ \n\t]*[0-9]*[ \n\t]*"));
  114.             if (s.contains(RXwhite))
  115.                 s=s.before(RXwhite);
  116.             if (fcompare(s,"pass")==0) {
  117.                 kind=pass;
  118.                 }
  119.             else if (fcompare(s,"resign")==0) {
  120.                 kind=resign;
  121.                 }
  122.             else {
  123.                 if (s.matches(Regex("[a-hj-tA-HJ-T][0-9]+"))&&Pos(s).valid()) {
  124.                     kind=play;
  125.                     where=Pos(s);
  126.                     }
  127.                 else {
  128.                     kind=invalid;
  129.                     }
  130.                 }
  131.             // ATTN movecomment="";
  132.             }
  133.     // members
  134.         bool Move::valid() {
  135.             return(kind!=invalid);
  136.             }
  137.         bool Move::operator== (Move vs) {
  138.             if (kind==invalid&&vs.kind==invalid) // if both are invalid...
  139.                 return(true);
  140.             if (kind==pass&&vs.kind==pass)       // or both are passes...
  141.                 return(true);
  142.             if (kind==resign&&vs.kind==resign)   // or both are resignations...
  143.                 return(true);
  144.             if (where==vs.where)                 // or if at same spot.
  145.                 return(true);
  146.             return(false);
  147.             }
  148.         String Move::str() {
  149.             // This is meant for interactive use,
  150.             //  not as an inverse for Move(String).
  151.             switch(kind) {
  152.                 case invalid:  return("Invalid move");       
  153.                 case play:     return(where.str());
  154.                 case pass:     return("Pass");                
  155.                 case resign:   return("Resign");               
  156.                 default:
  157.                     fatal("Problem in Move::str switch.");
  158.                 }
  159.             }
  160.  
  161. // class Block
  162.     Block::Block(Board& b, Pos p) {
  163.         int r=p.row;
  164.         int c=p.col;
  165.  
  166.         color=b.board[p.row][p.col];
  167.         stones.add(p);
  168.         if (r>0&&b.board[r-1][c]==empty)
  169.             liberties.add(Pos(r-1,c));
  170.         if (r<18&&b.board[r+1][c]==empty)
  171.             liberties.add(Pos(r+1,c));
  172.         if (c>0&&b.board[r][c-1]==empty)
  173.             liberties.add(Pos(r,c-1));
  174.         if (c<18&&b.board[r][c+1]==empty)
  175.             liberties.add(Pos(r,c+1));
  176.         }
  177.     Block::Block(Block& b) {
  178.         stones=b.stones;
  179.         liberties=b.liberties;
  180.         color=b.color;
  181.         }
  182.     Block::Block() {
  183.         }
  184.     String Block::str() {
  185.         String s(color==blackstone?"Black":"White");
  186.         s+=" block, libs {";
  187.         Pix p;
  188.         foreach(p,liberties)
  189.             s+=liberties(p).str()+" ";
  190.         s+="}, stones {";
  191.         foreach(p,stones)
  192.             s+=stones(p).str()+" ";
  193.         s+="}";
  194.         return(s);
  195.         }
  196.  
  197. // class Board
  198.     // class Board is the workhorse.  It represents the state of the game at a
  199.     // certain time, not just the way the board looks.
  200.     // globals
  201.         static Pos Board_tspot;         // used for ko identification
  202.     // constructors and destructors
  203.         Board::Board(String hand="") {
  204.             // Constructor for board representing beginning of game.
  205.             // Handicap stones for black are in Korschelt in the string.
  206.             // If there are handicap stones, white gets next move,
  207.             // else if string is null then black plays.
  208.             int i,j;
  209.             doboard(i,j) {
  210.                 board[i][j]=empty;
  211.                 blocks[i][j]=0;
  212.                 }
  213.             whitetaken=0;
  214.             blacktaken=0;
  215.             korestriction=Pos();    // illegal position means 'no restriction'
  216.             movenum=1;
  217.             gameover=false;
  218.             if (hand=="") {
  219.                 comment="At beginning of game, no handicap.";
  220.                 turntoplay=black;
  221.                 }
  222.             else {
  223.                 // a poorly formatted handicap string will GIGO...
  224.                 String parsed[hand.length()];
  225.                 int num=split(hand,parsed,hand.length(),RXwhite);
  226.                 comment="At beginning of game, " 
  227.                     + String(dec(num)) + " stones placed.";
  228.                 for (int i=0;i<num;i++) {
  229.                     Pos p(parsed[i]);
  230.                     turntoplay=black;
  231.                     applymove(Move(play,p));
  232.                     movenum=1;
  233.                     }
  234.                 turntoplay=white;
  235.                 }
  236.             }
  237.     // members
  238.         void Board::operator=(Board& b) {
  239.             board=b.board;
  240.             allBlocks=b.allBlocks;
  241.             int i,j;
  242.             doboard(i,j)
  243.                 blocks[i][j]=0;
  244.             Pix p,q;
  245.             foreach(p,allBlocks) {
  246.                 PosSLSet& pr=allBlocks(p).stones;
  247.                 foreach(q,pr) {
  248.                     Pos& qr=pr(q);
  249.                     blocks[qr.row][qr.col]=p;
  250.                     }
  251.                 }
  252.             movenum=b.movenum;
  253.             korestriction=b.korestriction;
  254.             gameover=b.gameover;
  255.             comment=b.comment;
  256.             turntoplay=b.turntoplay;
  257.             whitetaken=b.whitetaken;
  258.             blacktaken=b.blacktaken;
  259.             }
  260.         Board::Board(Board& b) {
  261.             (*this)=b;
  262.             }
  263.         char Board_chartoshow(int row, int col, boardenum what) {
  264.             // utility function for deciding character to display for different
  265.             // places on the board.  Not really a member function.
  266.             if (row==0&&col==0) {
  267.                 switch(what) {
  268.                     case empty: return('`'); break;
  269.                     case black: return('@'); break;
  270.                     case white: return('O'); break;
  271.                     default: fatal("Bad value in chartoshow switch.");
  272.                     }
  273.                 }
  274.             else if (row==0&&col==18) {
  275.                 switch(what) {
  276.                     case empty: return('\''); break;
  277.                     case black: return('@'); break;
  278.                     case white: return('O'); break;
  279.                     default: fatal("Bad value in chartoshow switch.");
  280.                     }
  281.                 }
  282.             else if (row==18&&col==0) {
  283.                 switch(what) {
  284.                     case empty: return('.'); break;
  285.                     case black: return('@'); break;
  286.                     case white: return('O'); break;
  287.                     default: fatal("Bad value in chartoshow switch.");
  288.                     }
  289.                 }
  290.             else if (row==18&&col==18) {
  291.                 switch(what) {
  292.                     case empty: return('.'); break;
  293.                     case black: return('@'); break;
  294.                     case white: return('O'); break;
  295.                     default: fatal("Bad value in chartoshow switch.");
  296.                     }
  297.                 }
  298.             else if (row==0||row==18) {
  299.                 switch(what) {
  300.                     case empty: return('-'); break;
  301.                     case black: return('@'); break;
  302.                     case white: return('O'); break;
  303.                     default: fatal("Bad value in chartoshow switch.");
  304.                     }
  305.                 }
  306.             else if (col==0||col==18) {
  307.                 switch(what) {
  308.                     case empty: return('|'); break;
  309.                     case black: return('@'); break;
  310.                     case white: return('O'); break;
  311.                     default: fatal("Bad value in chartoshow switch.");
  312.                     }
  313.                 }
  314.             else {
  315.                 switch(what) {
  316.                     case empty: return('+'); break;
  317.                     case black: return('@'); break;
  318.                     case white: return('O'); break;
  319.                     default: fatal("Bad value in chartoshow switch.");
  320.                     }
  321.                 }
  322.             }
  323.         String Board::str() {
  324.             // Make a string representing board.  This spans multiple lines,
  325.             // of course, and contains capturing info and so on.
  326.             String s="    a b c d e f g h j k l m n o p q r s t\n";
  327.             for (int i=0;i<19;i++) {
  328.                 s+=form(" %2d ",19-i);
  329.                 for (int j=0;j<19;j++) {
  330.                     s+=(Board_chartoshow(18-i,j,board[18-i][j]));
  331.                     if (j<18)
  332.                         s+=('-');
  333.                     }
  334.                 s+=form(" %-2d  ",19-i);
  335.                 switch(i) {
  336.                     case 0:
  337.                         if (gameover)
  338.                             s+="Game over.\n";
  339.                         else
  340.                             s+=form("Move #%d: %s to play.\n",movenum,
  341.                                          (turntoplay==black)?"black":"white");
  342.                         break;
  343.                     case 2:
  344.                         s+=form("White taken: %d\n", whitetaken);
  345.                         break;
  346.                     case 3:
  347.                         s+=form("Black taken: %d\n", blacktaken);
  348.                         break;
  349.                     case 5:
  350.                         if (korestriction.valid())
  351.                             s+="Ko restriction at "+korestriction.str()+".";
  352.                         s+="\n";
  353.                         break;
  354.                     default: 
  355.                         s+="\n";
  356.                     }
  357.                 }    
  358.             s+="    a b c d e f g h j k l m n o p q r s t\n\n";
  359.             s+=comment+"\n";
  360.             /*
  361.             s+="\n";
  362.             Pix blist[300];
  363.             int numblocks=0;
  364.             Pix p;
  365.             foreach(p,allBlocks) {
  366.                 s+=String('a'+numblocks)+": "+allBlocks(p).str()+"\n";
  367.                 blist[numblocks++]=p;
  368.                 }
  369.             for (i=0;i<19;i++) {
  370.                 for (int j=0;j<19;j++)
  371.                     s+=(char)(board[18-i][j]==empty?'.':('0'+libsat(Pos(18-i,j))));
  372.                 s+="  ";
  373.                 for (j=0;j<19;j++)
  374.                     s+=(char)(board[18-i][j]==empty?'.':('0'+sizeat(Pos(18-i,j))));
  375.                 s+="  ";
  376.                 for (j=0;j<19;j++) {
  377.                     bool found=false;
  378.                     if (blocks[18-i][j]!=0)
  379.                         for (int b=0;b<numblocks;b++) {
  380.                             if (blist[b]==blocks[18-i][j]) {
  381.                                 s+='a'+b;
  382.                                 found=true;
  383.                                 }
  384.                             }
  385.                     if (!found) s+='.';
  386.                     }
  387.                 s+="\n";
  388.                 }
  389.             s+="\n";
  390.             */
  391.             return(s);
  392.             }
  393.         int Board::sizeat(Pos p) {
  394.             // sizeat returns size of group at p.
  395.             // if invalid, this is just zero.
  396.             if (!p.valid())
  397.                 return(0);
  398.             if (blocks[p.row][p.col]==0)
  399.                 return(0);
  400.             else
  401.                 return(allBlocks(blocks[p.row][p.col]).stones.length());
  402.             }
  403.         int Board::libsat(Pos p) {
  404.             // libsat returns number of liberties of group at p.
  405.             // If p is empty, or invalid, this is 0.
  406.             if (!p.valid())
  407.                 return(0);
  408.             if (blocks[p.row][p.col]==0)
  409.                 return(0);
  410.             else
  411.                 return(allBlocks(blocks[p.row][p.col]).liberties.length());
  412.             }
  413.         bool Board::islegalmove(Move m) {
  414.             // islegalmove returns true, for legal moves, false for illegal.  
  415.             if (m.kind==invalid)          // if move is legit...
  416.                 return(false);
  417.             if (gameover)              // and game is still going...
  418.                 return(false);
  419.             if (m.kind==pass)              // and a pass...
  420.                 return(true);
  421.             if (board[m.where.row][m.where.col]!=empty)
  422.                 return(false);                // BUT if not empty, no good
  423.             if (korestriction==m.where)          // or if a ko restriction.
  424.                 return(false);
  425.             
  426.             if (allowsuicide)              // If suicide ok, then fine
  427.                 return(true);              // from here on in.
  428.         
  429.             // Now check if it's a suicide move. 
  430.             fatal("Suicide move disable not installed.");
  431.             return(true);
  432.             }
  433.         void Board::applymove(Move m) {
  434.             // apply move to board.  This is a workhorse.
  435.  
  436.             int before=whitetaken+blacktaken;
  437.             int r=m.where.row, c=m.where.col;
  438.             stoneenum othercolor;
  439.             if (turntoplay==blackstone)
  440.                 othercolor=whitestone;
  441.             else
  442.                 othercolor=blackstone;
  443.  
  444.             if (m.kind==resign) {    // resigns are pretty easy to deal with
  445.                 gameover=true;
  446.                 return;
  447.                 }
  448.             else if (m.kind==play) {
  449.                 if (errortest)        // check for error if turned on
  450.                     if (!islegalmove(m))
  451.                         fatal("Illegal move in Board::applymove.");
  452.  
  453.                 // make a new block, and merge any that were adjacent
  454.                 board[r][c]=turntoplay;
  455.                 Pix myblock=allBlocks.prepend(Block(*this,m.where));
  456.                 blocks[r][c]=myblock;
  457.                 if (r>0&&board[r-1][c]==turntoplay)
  458.                     transfer(blocks[r-1][c],myblock);
  459.                 if (r<18&&board[r+1][c]==turntoplay)
  460.                     transfer(blocks[r+1][c],myblock);
  461.                 if (c>0&&board[r][c-1]==turntoplay)
  462.                     transfer(blocks[r][c-1],myblock);
  463.                 if (c<18&&board[r][c+1]==turntoplay)
  464.                     transfer(blocks[r][c+1],myblock);
  465.  
  466.                 // remove this position from liberty lists of adjacents                
  467.                 if (r>0&&blocks[r-1][c]!=0) {
  468.                     PosSLSet& s=allBlocks(blocks[r-1][c]).liberties;
  469.                     s.del(m.where);
  470.                     if (board[r-1][c]==othercolor&&s.length()==0)
  471.                         removeblock(blocks[r-1][c]);
  472.                     }
  473.                 if (r<18&&blocks[r+1][c]!=0) {
  474.                     PosSLSet& s=allBlocks(blocks[r+1][c]).liberties;
  475.                     s.del(m.where);
  476.                     if (board[r+1][c]==othercolor&&s.length()==0)
  477.                         removeblock(blocks[r+1][c]);
  478.                     }
  479.                 if (c>0&&blocks[r][c-1]!=0) {
  480.                     PosSLSet& s=allBlocks(blocks[r][c-1]).liberties;
  481.                     s.del(m.where);
  482.                     if (board[r][c-1]==othercolor&&s.length()==0)
  483.                         removeblock(blocks[r][c-1]);
  484.                     }
  485.                 if (c<18&&blocks[r][c+1]!=0) {
  486.                     PosSLSet& s=allBlocks(blocks[r][c+1]).liberties;
  487.                     s.del(m.where);
  488.                     if (board[r][c+1]==othercolor&&s.length()==0)
  489.                         removeblock(blocks[r][c+1]);
  490.                     }
  491.  
  492.                 // make sure not a suicide
  493.                 if (allBlocks(myblock).liberties.length()==0)
  494.                     removeblock(myblock);
  495.  
  496.                 // just one stone taken; ko?
  497.                 if (whitetaken+blacktaken==before+1&& sizeat(m.where)==1
  498.                         && libsat(m.where)==1)
  499.                     korestriction=Board_tspot;
  500.                 else
  501.                     korestriction=Pos();
  502.                 }
  503.             turntoplay=othercolor;
  504.             movenum++;
  505.             }
  506.         void Board::removeblock(Pix bl) {
  507.             Block& b=allBlocks(bl);
  508.             if (b.color==blackstone)
  509.                 blacktaken+=b.stones.length();
  510.             else 
  511.                 whitetaken+=b.stones.length();
  512.             Pix p;
  513.             foreach(p,b.stones) {
  514.                 Pos w(b.stones(p));
  515.                 Board_tspot=w;
  516.  
  517.                 board[w.row][w.col]=empty;
  518.                 blocks[w.row][w.col]=0;
  519.  
  520.                 // for each potential adjacent block, try to add
  521.                 // this as a liberty
  522.                 if (w.row>0&&blocks[w.row-1][w.col]!=0)
  523.                     allBlocks(blocks[w.row-1][w.col]).liberties.add(w);
  524.                 if (w.row<18&&blocks[w.row+1][w.col]!=0)
  525.                     allBlocks(blocks[w.row+1][w.col]).liberties.add(w);
  526.                 if (w.col>0&&blocks[w.row][w.col-1]!=0)
  527.                     allBlocks(blocks[w.row][w.col-1]).liberties.add(w);
  528.                 if (w.col<18&&blocks[w.row][w.col+1]!=0)
  529.                     allBlocks(blocks[w.row][w.col+1]).liberties.add(w);
  530.                 }
  531.             allBlocks.del(bl);
  532.             }
  533.         void Board::transfer(Pix from, Pix to) {
  534.             if (from==to)
  535.                 return;
  536.             Block& bfrom=allBlocks(from);
  537.             Block& bto=allBlocks(to);
  538.             Pix p;
  539.             foreach(p,bfrom.stones) {
  540.                 Pos t=bfrom.stones(p);
  541.                 blocks[t.row][t.col]=to;
  542.                 }
  543.             bto.stones|=bfrom.stones;
  544.             bto.liberties|=bfrom.liberties;
  545.             allBlocks.del(from);
  546.             }
  547. // class Game
  548.     // class Game represents a (possibly unfinished) game. 
  549.     // Score, etc. when known.
  550.     // globals used by class Game
  551.         Regex Game_avoidables =
  552.             "^[ \n\t]*\\(\\(!\\)\\|\\(UNMARK\\)\\|\\($\\)\\|\\(\\*\\)\\|\\(EVENT\\)\\|\\(BOARDSIZE\\)\\|\\(MARK\\)\\|\\(PRISONER\\)\\)";
  553.         Regex Game_begins = "^[ \n\t]*\\(\\(COM\\)\\|\\(VAR\\)\\)";
  554.         Regex Game_ends =   "^[ \n\t]*\\(\\(ENDCOM\\)\\|\\(ENDVAR\\)\\)";
  555.     // constructor
  556.         Game::Game(String filename) {
  557.             // Try to load game 'filename'.
  558.             // If a problem, such as file not readable,
  559.             // fatal error.
  560.             komi=0.0; // ATTN: komi, score are unparsed (not in Ishi format).
  561.             comment="Uninitialized comment for file "+filename;
  562.             result=unknown;
  563.             finalscore=0.0;
  564.             currentidx=0;
  565.             nummoves=0;
  566.             
  567.             #ifndef IRIX
  568.             File f(filename,io_readonly,a_useonly);
  569.             if (!f.readable()) {
  570.                 fatal("Could not open file "+filename+".");
  571.                 }
  572.             #else /* IRIX */
  573.             FILE *fp;
  574.             fp=fopen((char*)("games/"+filename),"r");
  575.             #endif /* IRIX */
  576.         
  577.             #ifndef IRIX
  578.             char *cp;
  579.             #else /* IRIX */
  580.             char cp[1000];
  581.             #endif /* IRIX */
  582.             String s;
  583.             int level=0,line=0;
  584.             currentidx=0;        
  585.             #ifndef IRIX
  586.             f.gets(&cp);
  587.             s=cp;
  588.             free(cp);
  589.             while (!f.eof()) {
  590.             #else /* IRIX */
  591.             while (fgets(cp,1000,fp)!=NULL) {
  592.                 cp[strlen(cp)-1]='\0';
  593.                 s=cp;
  594.             #endif /* IRIX */
  595.                 line++;
  596.                 #ifdef IRIX
  597.                 if (s.length()==0)
  598.                     continue;
  599.                 #endif /* IRIX */
  600.                 if (!s.contains(Game_avoidables)) {
  601.                     if (s.contains(Game_begins))
  602.                         level++;
  603.                     else if (s.contains(Game_ends))
  604.                         level--;
  605.                     else if (s.contains("SETUP B")&&level==0&&nummoves==0) {
  606.                         s=s.after("SETUP B");
  607.                         while (s[0]==' ')
  608.                             s.at(0,1)="";
  609.                         current=Board(s);
  610.                         handicap=s;
  611.                         }
  612.                     else if (level==0) {
  613.                         Move m(s);
  614.                         if (!m.valid()) {
  615.                             fatal("Illegal syntax in " + filename + " line #" 
  616.                                           + String(dec(line)) + ": " + s);
  617.                             }
  618.                         if (nummoves>Game_maxmoves) {
  619.                             fatal("Too many moves to load in.");
  620.                             }
  621.                         nummoves++;
  622.                         if (!current.islegalmove(m)) {
  623.                             fatal("Illegal move in " + filename + " line #"
  624.                                           + String(dec(line)) + ": " + s);
  625.                             }
  626.                         current.applymove(m);
  627.                         currentidx++;
  628.                         moves[nummoves-1]=m;
  629.                         }
  630.                     }
  631.                 #ifndef IRIX
  632.                 f.gets(&cp);
  633.                 s=cp;
  634.                 free(cp);
  635.                 #endif /* IRIX */
  636.                 }
  637.             #ifdef IRIX
  638.             //cout << filename << " had " << dec(nummoves) << "\n";
  639.             fclose(fp);
  640.             #endif /* IRIX */
  641.             comment="Game loaded from file " + filename;
  642.             }
  643.     // members
  644.         float Game::score() {
  645.             return(finalscore - komi);
  646.             }
  647.         Board& Game::snapshot(int idx) {
  648.             while (currentidx!=idx) {
  649.                 if (idx<currentidx) {
  650.                     current=Board(handicap);
  651.                     currentidx=0;
  652.                     break;
  653.                     }
  654.                 current.applymove(moves[currentidx]);
  655.                 currentidx++;
  656.                 if (currentidx>nummoves)
  657.                     fatal("Off the move list in snapshot.");
  658.                 }
  659.             return current;
  660.             };
  661.         String Game::str() {
  662.             String s;
  663.             s+="EVENT\nBOARDSIZE 19\nCOM\n";
  664.             s+=form("Komi: %g\n",komi);
  665.             s+=form("Score: %g\n",score());
  666.             s+="Handicap: "+handicap+"\n";
  667.             s+="Result: ";
  668.             switch(result) {
  669.                 case blacklost:      s+="Black lost";     break;
  670.                 case whitelost:      s+="White lost";     break;
  671.                 case blackresigned:  s+="Black resigned"; break;
  672.                 case whiteresigned:  s+="White resigned"; break;
  673.                 case unfinished:     s+="Unfinished";     break;
  674.                 case tied:           s+="Tied";           break;
  675.                 case unknown:        s+="Unknown";        break;
  676.                 default:
  677.                     fatal("Bad in result switch: " + String(dec(result))+".");
  678.                 }
  679.             s+="\nComment: "+comment;
  680.             s+=form("\nNumber of moves: %d\n",nummoves);
  681.             s+="Board at end of game:\n";
  682.             s+=snapshot(nummoves).str();
  683.             if (handicap!="")
  684.                 s+="SETUP B "+handicap+"\n";
  685.             s+="ENDCOM\n";
  686.             for (int i=0;i<nummoves;i++)
  687.                 s+=form("%c %d ",(snapshot(i).turntoplay==blackstone)?'B':'W',i+1);
  688.                 s+=moves[i].str()+"\n";
  689.             return(s);
  690.             }
  691.